#include <iostream>
#include <emscripten.h>
#include <emscripten/bind.h>
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/stringbuffer.h"
#include "OCVolume/MarchingCubes/LookUpTable.h"
#include "OCVolume/MarchingCubes/MarchingCubes.h"
#include "OCVolume/MarchingCubes/MarchingCubes.cpp"
#include "Grid3dData.h"

class Image
{
public:
  float west = -180.0;
  float south = -90.0;
  float east = 180.0;
  float north = 90.0;
  int width = 0;
  int height = 0;
  int depth = 1;
  std::vector<float> data;
};

class IsoResult
{
public:
  emscripten::val position;
};

class IsoSurface
{
private:
  Image image;
  IsoSurface()
  {
  }

public:
  unsigned int bufferSize = 0;
  float scale = 1.0;
  float offset = 0.0;
  bool doneOffset = true;
  bool verbose = false;
  static IsoSurface *createInstance()
  {
    return new IsoSurface();
  }

  static void releaseInstance(IsoSurface *ptr)
  {
    delete ptr;
  }

  void setImage(const emscripten::val &jsImage)
  {
    image.west = jsImage["west"].as<float>();
    image.south = jsImage["south"].as<float>();
    image.east = jsImage["east"].as<float>();
    image.north = jsImage["north"].as<float>();
    const emscripten::val &source = jsImage["source"];
    if (source.isUndefined() || source.isNull())
      return;

    image.width = source["width"].as<int>();
    image.height = source["height"].as<int>();
    if (!(source["depth"].isUndefined() || source["depth"].isNull()))
    {
      image.depth = source["depth"].as<int>();
    }

    int width = image.width;
    int height = image.height;
    int depth = image.depth;

    size_t dataSize = width * height * depth;

    int pixelDatatype = source["pixelDatatype"].as<int>();

    const emscripten::val &arrayBufferView = source["arrayBufferView"];
    if (arrayBufferView.isUndefined() || arrayBufferView.isNull())
      return;
    int length = arrayBufferView["length"].as<int>();
    size_t comp = length / dataSize;
    float minValue = FLT_MAX;
    float maxValue = FLT_MIN;

    std::vector<uint8_t> vecCharData;
    std::vector<float> vecFloatData;

    bool isTypeByte = pixelDatatype == 5121;
    bool isTypeFloat = pixelDatatype == 5126;
    bool isTypeHalfFloat = pixelDatatype == 36193;

    if (isTypeByte)
    {
      vecCharData.resize(length);
      emscripten::val view(emscripten::typed_memory_view(
          vecCharData.size(),
          vecCharData.data()));
      view.call<void>("set", arrayBufferView);
    }
    else if (isTypeHalfFloat)
    {
      std::vector<uint16_t> vecShortData;
      vecShortData.resize(length);
      emscripten::val view(emscripten::typed_memory_view(
          vecShortData.size(),
          vecShortData.data()));
      view.call<void>("set", arrayBufferView);

      vecFloatData.resize(length);
      for (auto i = 0; i < vecShortData.size(); i++)
      {
        vecFloatData[i] = float16ToFloat32(vecShortData[i]) * scale + offset;
      }
    }
    else
    {
      vecFloatData.resize(length);
      emscripten::val view(emscripten::typed_memory_view(
          vecFloatData.size(),
          vecFloatData.data()));
      view.call<void>("set", arrayBufferView);
    }

    if (isTypeFloat && comp == 1)
    {
      image.data = std::move(vecFloatData);
      doneOffset = false;
    }
    else
    {
      image.data.resize(dataSize);
      for (int z = 0; z < depth; z++)
      {
        int zOff = z * width * height;
        for (int y = 0; y < height; y++)
        {
          int yOff = y * width;
          for (int x = 0; x < width; x++)
          {
            int i = zOff + yOff + x;

            if (isTypeByte)
            {
              if (comp == 4)
              {
                uint8_t u0 = vecCharData[i * comp];
                uint8_t u1 = vecCharData[i * comp + 1];
                uint8_t v0 = vecCharData[i * comp + 2];
                uint8_t v1 = vecCharData[i * comp + 3];
                float u = floor(u0 - 127) + u1 / 255.0;
                float v = floor(v0 - 127) + v1 / 255.0;
                image.data[i] = sqrt(u * u + v * v);
              }
              else
              {
                float v = vecCharData[i * comp] / 255.0 * scale + offset;
                image.data[i] = v;
              }
            }
            else
            {
              if (comp == 2)
              {
                float u = vecFloatData[i * comp];
                float v = vecFloatData[i * comp + 1];
                image.data[i] = sqrt(u * u + v * v);
              }
              else
              {
                // comp 3 4 use first element
                image.data[i] = vecFloatData[i * comp];
              }
            }

            const float &value = image.data[i];
            if (minValue > value)
              minValue = value;
            if (maxValue < value)
              maxValue = value;
          }
        }
      }
    }

    if (verbose)
    {
      std::cout << "comp:" << comp
                << " pixelDatatype:" << pixelDatatype
                << " offset:" << offset
                << " scale:" << scale
                << " width:" << width
                << " height:" << height
                << " depth:" << depth
                << " doneOffset:" << doneOffset
                << " minValue:" << minValue
                << " maxValue:" << maxValue << "\n";
    }
  }

  inline emscripten::val gen_typeArray(const std::vector<float> pointCoords)
  {
    auto view = emscripten::typed_memory_view(pointCoords.size(), pointCoords.data());
    return emscripten::val::global("Float32Array").new_(view);
  }

  emscripten::val getResult(const emscripten::val &jsIsoValues, int multiple = 1, int destImageDepth = 1000)
  {
    if (destImageDepth > image.depth || destImageDepth <= 0)
      destImageDepth = image.depth;

    float isoVal = jsIsoValues[0].as<float>();
    if (!doneOffset)
    {
      // back
      isoVal = (isoVal - offset) / scale;
    }

    size_t maxNumTriPerFace = (bufferSize * bufferSize * multiple) / 3;

    std::vector<float> positionVec;
    positionVec.resize(bufferSize * bufferSize * 3 * multiple);

    float widthScale = image.width - 1;
    float heightScale = image.height - 1;
    float depthScale = destImageDepth - 1;
    if (depthScale == 0.0)
      depthScale = 1.0;

    MarchingCubes mc;
    mc.set_ext_data((float *)&image.data[0]);
    mc.set_resolution(image.width, image.height, destImageDepth);
    mc.init_all();
    mc.run(isoVal);
    auto numTri = mc.ntrigs();
    float minValue = FLT_MAX;
    float maxValue = FLT_MIN;
    for (size_t k = 0; k < numTri && k < maxNumTriPerFace; k++)
    {
      auto &tri = mc.triangles()[k];
      auto &v0 = mc.vertices()[tri.v1];
      auto &v1 = mc.vertices()[tri.v2];
      auto &v2 = mc.vertices()[tri.v3];
      size_t i = k * 9;
      positionVec[i + 0] = v0.x / widthScale;
      positionVec[i + 1] = v0.y / heightScale;
      positionVec[i + 2] = v0.z / depthScale;
      positionVec[i + 3] = v1.x / widthScale;
      positionVec[i + 4] = v1.y / heightScale;
      positionVec[i + 5] = v1.z / depthScale;
      positionVec[i + 6] = v2.x / widthScale;
      positionVec[i + 7] = v2.y / heightScale;
      positionVec[i + 8] = v2.z / depthScale;
      maxValue = std::max(maxValue, v0.z);
      maxValue = std::max(maxValue, v1.z);
      maxValue = std::max(maxValue, v2.z);
      minValue = std::min(minValue, v0.z);
      minValue = std::min(minValue, v1.z);
      minValue = std::min(minValue, v2.z);
    }
    mc.clean_all();
    if (verbose)
    {
      std::cout << "minValue:" << minValue << "\t";
      std::cout << "maxValue:" << maxValue << "\t";
      std::cout << "total tri:" << numTri << "\t";
      std::cout << "limit tri:" << maxNumTriPerFace << "\n";
    }

    return gen_typeArray(positionVec);
  }
};
